package com.erlang4j.internal.messages;

import static com.erlang4j.api.Erlang4jMessageLanguage.binding;

import java.util.Iterator;

import com.ericsson.otp.erlang.OtpErlangAtom;
import com.ericsson.otp.erlang.OtpErlangList;
import com.ericsson.otp.erlang.OtpErlangObject;
import com.ericsson.otp.erlang.OtpErlangTuple;
import com.erlang4j.api.IBinding;
import com.erlang4j.api.exceptions.Erlang4jUnexpectedTokenException;

/**
 * Variables start with a capital and are bound atoms start with a lowercase and are expected {} marks a tuple [] marks
 * a list
 * <p>
 * Examples
 * <ul>
 * <li>"Any" will match a single object (string, integer, etc, maybe I will do lists latter), and return a binding "Any"
 * -> the value
 * <li>"{Pid, start}" will match a tuple with the second value an atom start, and return a binding "Pid" -> the value
 * </ul>
 * </p>
 * <p>
 * Design notes
 * <ul>
 * <li>If only java had a nice mechanism for mutual recursion!
 * <li>This uses the return stack to manage state, its a pretty simple parser
 * <li>The MessageComposer and this work very similarly. I have experimented with a walker pattern, but I could not make
 * the code understandable. In the future I might try again
 * </ul>
 * </p>
 * 
 * @author Phil Rice
 */
public class MessageParser implements IMessageParser {

	public IBinding parse(OtpErlangObject object, String input) {
		return parse(object, input, binding());
	}

	public IBinding parse(OtpErlangObject object, String input, IBinding binding) {
		Iterator<MessageToken> iterator = new MessageLexicalAnalyser(input).iterator();
		IBinding result = processToken(binding, iterator, object);
		return result;
	}

	private IBinding processToken(IBinding binding, Iterator<MessageToken> iterator, OtpErlangObject object) {
		if (!binding.isValid())
			return binding;
		MessageToken token = iterator.next();
		switch (token.type) {
		case atomName:
			if (object instanceof OtpErlangAtom)
				if (((OtpErlangAtom) object).atomValue().equals(token.value))
					return binding;
			break;
		case variable:
			return binding.bind(token.value, object);
		case openTuple:
			if (object instanceof OtpErlangTuple)
				return processTuple(binding, iterator, (OtpErlangTuple) object);
			break;
		case openList:
			if (object instanceof OtpErlangList)
				return processList(binding, iterator, (OtpErlangList) object);
			break;
		default:
			throw new Erlang4jUnexpectedTokenException(token);
		}
		return Binding.invalid;
	}

	private IBinding processList(IBinding binding, Iterator<MessageToken> iterator, OtpErlangList list) {
		OtpErlangObject[] elements = list.elements();
		for (OtpErlangObject object : elements) {
			binding = processToken(binding, iterator, object);
		}
		return eatToken(binding, iterator, MessageTokenType.closeList);
	}

	private IBinding processTuple(IBinding binding, Iterator<MessageToken> iterator, OtpErlangTuple tuple) {
		OtpErlangObject[] elements = tuple.elements();
		for (OtpErlangObject object : elements) {
			binding = processToken(binding, iterator, object);
		}
		return eatToken(binding, iterator, MessageTokenType.closeTuple);

	}

	private IBinding eatToken(IBinding binding, Iterator<MessageToken> iterator, MessageTokenType expectedTokenType) {
		if (iterator.hasNext()) {
			MessageToken tokenToEat = iterator.next();
			if (tokenToEat.type == expectedTokenType)
				return binding;
			else
				return Binding.invalid;
		}
		return Binding.invalid;
	}

}
